// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: leon
#pragma once

#include <iostream>
#include <memory>
#include <optional>
#include <set>
#include <sstream>
#include <string>
#include <vector>

#include <boost/date_time/posix_time/posix_time.hpp>

#include "coin2/exchange/base/symbology/derivative_util.h"
#include "coin2/exchange/base/symbology/product.h"
#include "coin2/exchange/phemex_futures/symbology/currency.h"

namespace coin2::exchange::phemex_futures::symbology {
using ExchangeType = base::market::ExchangeType;
using MarketType = base::market::MarketType;
using ContractType = coin::proto::ContractType;
using IProduct = coin2::exchange::base::symbology::IProduct;
using ICurrency = coin2::exchange::base::symbology::ICurrency;
template <typename CurrencyT>
using CurrencyPairProduct = coin2::exchange::base::symbology::CurrencyPairProduct<CurrencyT>;
using coin2::exchange::base::symbology::ContractTypeToString;
using coin2::exchange::phemex_futures::symbology::PhemexFuturesCurrency;

// specify product for phemex futures for handling special cases
template <typename CurrencyT, typename ProductT>
class FuturesProduct : public CurrencyPairProduct<CurrencyT> {
 public:
  FuturesProduct(
      const CurrencyT& base,
      const CurrencyT& quote,
      const std::string& native_product,
      const std::string& native_subscription_symbol,
      const ExchangeType exchange,
      const coin::proto::ContractType contract_type,
      const int64_t expiry_timestamp,
      const int64_t resolve_timestamp)
      : CurrencyPairProduct<CurrencyT>(
            base,
            quote,
            ConvertToFuturesNormStr(base, quote, expiry_timestamp, native_product),
            native_product,
            ConvertToFuturesSubscriptionSymbol(base, quote, contract_type, native_product),
            native_subscription_symbol,
            MarketType::Futures,
            exchange),
        contract_type_(contract_type),
        expiry_timestamp_(expiry_timestamp),
        resolve_timestamp_(resolve_timestamp),
        expiry_str_(ConvertToExpiryStr(expiry_timestamp_)) {}

 public:
  int64_t expiry() const { return expiry_timestamp_; }

  const std::string& expiry_str() const { return expiry_str_; }

  coin::proto::ContractType contract_type() const { return contract_type_; }

  const std::string& contract_type_name() const { return ContractTypeToString(contract_type_); }

  int64_t resolve_timestamp() const { return resolve_timestamp_; }

  static std::unique_ptr<ProductT> FromNormStrImpl(
      const std::string& product_str,
      const int64_t resolve_timestamp) {
    std::string base_str;
    std::string quote_str;
    std::string expiry_str;

    std::vector<std::string> out;
    out = Split(product_str, "-.");
    if (out.size() != 3) {
      return {};
    }
    base_str = out.at(0);
    quote_str = out.at(1);
    expiry_str = out.at(2);

    auto base = CurrencyT::FromNormStrNoThrow(base_str, resolve_timestamp);
    auto quote = CurrencyT::FromNormStrNoThrow(quote_str, resolve_timestamp);

    if (!base || !quote) {
      return {};
    }
    std::string native_product;
    std::string native_subscription_symbol;
    coin::proto::ContractType contract_type;
    int64_t expiry_timestamp;

    bool success = ProductT::CalculateParams(
        *base,
        *quote,
        expiry_str,
        resolve_timestamp,
        &native_product,
        &native_subscription_symbol,
        &contract_type,
        &expiry_timestamp);

    if (!success) {
      return {};
    }

    if (base->native_currency() == "USD") {
      return std::make_unique<ProductT>(
          *quote,
          *base,
          native_product,
          native_subscription_symbol,
          contract_type,
          expiry_timestamp,
          resolve_timestamp);
    } else {
      return std::make_unique<ProductT>(
          *base,
          *quote,
          native_product,
          native_subscription_symbol,
          contract_type,
          expiry_timestamp,
          resolve_timestamp);
    }
  }

  IProduct* Clone() const final {
    return new ProductT(
        CurrencyPairProduct<CurrencyT>::base_impl(),
        CurrencyPairProduct<CurrencyT>::quote_impl(),
        CurrencyPairProduct<CurrencyT>::native_product(),
        CurrencyPairProduct<CurrencyT>::feed_native_symbol(),
        contract_type(),
        expiry_timestamp_,
        resolve_timestamp_);
  }

 private:
  static std::string ConvertToFuturesNormStr(
      const ICurrency& base,
      const ICurrency& quote,
      const int64_t expiry_timestamp,
      const std::string native_product) {
    std::stringstream ss;
    if (native_product == "BTCUSD" || native_product == "cETHUSD") {
      ss << quote.currency() << "-" << base.currency() << ".PERPETUAL";
    } else {
      ss << base.currency() << "-" << quote.currency() << ".PERPETUAL";
    }
    return ss.str();
  }

  static std::string ConvertToFuturesSubscriptionSymbol(
      const ICurrency& base,
      const ICurrency& quote,
      const coin::proto::ContractType contract_type,
      const std::string native_product) {
    std::stringstream ss;
    if (native_product == "BTCUSD" || native_product == "cETHUSD") {
      ss << quote.currency() << "-" << base.currency() << ".PERPETUAL";
    } else {
      ss << base.currency() << "-" << quote.currency() << ".PERPETUAL";
    }
    return ss.str();
  }

  static bool ParseFuturesNormStr(
      const std::string& futures_norm_str,
      std::string* base,
      std::string* quote,
      std::string* expiry_str) {
    std::vector<std::string> out;
    out = Split(futures_norm_str, "-.");
    if (out.size() == 3 || out.size() == 4) {
      *base = out.at(0);
      *quote = out.at(1);
      *expiry_str = out.at(2);
      return true;
    }
    return false;
  }

  static std::string ConvertToExpiryStr(int64_t expiry_timestamp) {
    if (expiry_timestamp == PERPETUAL_TIMESTAMP) {
      return "PERPETUAL";
    } else if (expiry_timestamp == IMMEDIATE_TIMESTAMP) {
      return "IMMEDIATE";
    } else {
      return base::symbology::ToDateStr(expiry_timestamp);
    }
  }

 public:
  static const int64_t PERPETUAL_TIMESTAMP = 4102416000LL * 1000000000LL;
  static const int64_t IMMEDIATE_TIMESTAMP = 0;

 private:
  const coin::proto::ContractType contract_type_;
  const int64_t expiry_timestamp_;
  const int64_t resolve_timestamp_;
  const std::string expiry_str_;
};

class PhemexFuturesProduct : public FuturesProduct<PhemexFuturesCurrency, PhemexFuturesProduct>,
                             public base::symbology::ProductFromStrMixin<PhemexFuturesProduct> {
 public:
  using FuturesProduct = FuturesProduct<PhemexFuturesCurrency, PhemexFuturesProduct>;
  using ContractType = base::symbology::ContractType;

 public:
  PhemexFuturesProduct(
      const PhemexFuturesCurrency& base,
      const PhemexFuturesCurrency& quote,
      const std::string& native_product,
      const std::string& native_subscription_symbol,
      const ContractType contract_type,
      const int64_t expiry_timestamp,
      const int64_t resolve_timestamp)
      : FuturesProduct(
            base,
            quote,
            native_product,
            native_subscription_symbol,
            base::market::ExchangeType::Phemex,
            contract_type,
            expiry_timestamp,
            resolve_timestamp) {}

 public:
  static std::unique_ptr<PhemexFuturesProduct> FromNativeStrImpl(
      const std::string& native_product,
      const int64_t resolve_timestamp) {
    std::vector<std::string> out;
    std::unique_ptr<PhemexFuturesCurrency> base;
    std::unique_ptr<PhemexFuturesCurrency> quote;

    if (native_product == "BTCUSD") {
      base = PhemexFuturesCurrency::FromNativeStrNoThrow("BTC", resolve_timestamp);
      quote = PhemexFuturesCurrency::FromNativeStrNoThrow("USD", resolve_timestamp);
    } else if (native_product == "uBTCUSD") {
      base = PhemexFuturesCurrency::FromNativeStrNoThrow("BTC", resolve_timestamp);
      quote = PhemexFuturesCurrency::FromNativeStrNoThrow("USD", resolve_timestamp);
    } else if (native_product == "cETHUSD") {
      base = PhemexFuturesCurrency::FromNativeStrNoThrow("ETH", resolve_timestamp);
      quote = PhemexFuturesCurrency::FromNativeStrNoThrow("USD", resolve_timestamp);
    } else if (StringEndsWith(native_product, "USD")) {
      std::string base_str = native_product.substr(0, native_product.find("USD"));
      base = PhemexFuturesCurrency::FromNativeStrNoThrow(base_str, resolve_timestamp);
      quote = PhemexFuturesCurrency::FromNativeStrNoThrow("USD", resolve_timestamp);
    } else if (StringEndsWith(native_product, "USDT")) {
      std::string base_str = native_product.substr(0, native_product.find("USDT"));
      base = PhemexFuturesCurrency::FromNativeStrNoThrow(base_str, resolve_timestamp);
      quote = PhemexFuturesCurrency::FromNativeStrNoThrow("USDT", resolve_timestamp);
    } else {
      return {};
    }

    if (!base || !quote) {
      return {};
    }
    std::string expiry_str = "PERPETUAL";

    std::string native_product_tmp;
    std::string native_subscription_symbol;
    coin::proto::ContractType contract_type;
    int64_t expiry_timestamp;
    bool success = false;
    if (native_product == "BTCUSD" || native_product == "cETHUSD") {
      success = CalculateParams(
          *quote,
          *base,
          expiry_str,
          resolve_timestamp,
          &native_product_tmp,
          &native_subscription_symbol,
          &contract_type,
          &expiry_timestamp);
    } else {
      success = CalculateParams(
          *base,
          *quote,
          expiry_str,
          resolve_timestamp,
          &native_product_tmp,
          &native_subscription_symbol,
          &contract_type,
          &expiry_timestamp);
    }

    if (!success) {
      return {};
    }

    CHECK_EQ(native_product_tmp, native_product);
    return std::make_unique<PhemexFuturesProduct>(
        *base,
        *quote,
        native_product,
        native_subscription_symbol,
        contract_type,
        expiry_timestamp,
        resolve_timestamp);
  }

  static bool CalculateParams(
      const PhemexFuturesCurrency& base,
      const PhemexFuturesCurrency& quote,
      const std::string& expiry_str,
      const int64_t resolve_timestamp,
      std::string* native_product,
      std::string* native_subscription_symbol,
      ContractType* contract_type,
      int64_t* expiry_timestamp) {
    using base::symbology::ContractType;
    using base::symbology::FindExpiryFriday;
    using base::symbology::IsDateStr;
    using base::symbology::StringToContractType;
    using base::symbology::ToDateStr;

    if (IsDateStr(expiry_str)) {
      return false;
    }
    auto type = StringToContractType(expiry_str);
    if (!type) {
      return false;
    }

    *contract_type = type.value();
    if (*contract_type != ContractType::PERPETUAL) {
      return false;
    }
    *expiry_timestamp = FuturesProduct::PERPETUAL_TIMESTAMP;

    std::stringstream ss;
    if (base.native_currency() == "USD" && quote.native_currency() == "BTC") {
      ss << "BTCUSD";
    } else if (base.native_currency() == "BTC" && quote.native_currency() == "USD") {
      ss << "uBTCUSD";
    } else if (base.native_currency() == "USD" && quote.native_currency() == "ETH") {
      ss << "cETHUSD";
    } else {
      ss << base.native_currency() << quote.native_currency();
    }
    *native_product = ss.str();
    *native_subscription_symbol = *native_product;
    return true;
  }
};

}  // namespace coin2::exchange::phemex_futures::symbology
